/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package net.vinote.smart.platform.module.log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;
import java.util.logging.XMLFormatter;

public class SmartLoggerHandler extends StreamHandler {
	private boolean append;

	private int limit; // zero => no limit.

	private String pattern;

	private String outFile;

	private String errFile;
	private String console;
	InnerRandomAccessFile randomFile = null;
	private boolean doneHeader = false;

	private void open(File fname) throws IOException {
		randomFile = new InnerRandomAccessFile(fname, "rwd");
	}

	private void configure() {
		LogManager manager = LogManager.getLogManager();

		String cname = getClass().getName();

		pattern = getStringProperty(manager.getProperty(cname + ".pattern"),
				"%h/java%u.log");
		limit = getIntProperty(manager.getProperty(cname + ".limit"), 0);
		if (limit < 0) {
			limit = 0;
		}
		append = getBooleanProperty(manager.getProperty(cname + ".append"),
				false);
		outFile = getStringProperty(manager.getProperty(cname + ".out"), null);
		errFile = getStringProperty(manager.getProperty(cname + ".err"), null);
		console = getStringProperty(manager.getProperty(cname + ".console"),
				null);
		setLevel(getLevelProperty(manager.getProperty(cname + ".level"),
				Level.ALL));
		setFilter(getFilterProperty(manager.getProperty(cname + ".filter"),
				null));
		setFormatter(getFormatterProperty(
				manager.getProperty(cname + ".formatter"), new XMLFormatter()));
		try {
			setEncoding(getStringProperty(
					manager.getProperty(cname + ".encoding"), null));
		} catch (Exception ex) {
			try {
				setEncoding(null);
			} catch (Exception ex2) {
				ex2.printStackTrace();
			}
		}
		if (outFile != null) {
			try {
				System.setOut(new PrintStream(new FileOutputStream(new File(
						outFile), append), true));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
		if (errFile != null) {
			try {
				System.setErr(new PrintStream(new FileOutputStream(new File(
						errFile), append), true));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
		if (console != null) {
			setOutputStream(System.err);
		}
	}

	public SmartLoggerHandler() throws IOException, SecurityException {
		configure();
		openFiles();
		doneHeader = false;
	}

	private void openFiles() throws IOException {
		LogManager manager = LogManager.getLogManager();
		manager.checkAccess();
		if (limit < 0) {
			limit = 0;
		}

		InitializationErrorManager em = new InitializationErrorManager();
		setErrorManager(em);

		if (append) {
			open(new File(pattern));
		} else {
			rotate();
		}

		Exception ex = em.lastException;
		if (ex != null) {
			if (ex instanceof IOException) {
				throw (IOException) ex;
			} else if (ex instanceof SecurityException) {
				throw (SecurityException) ex;
			} else {
				throw new IOException("Exception: " + ex);
			}
		}

		setErrorManager(new ErrorManager());
	}

	private synchronized void rotate() {
		Level oldLevel = getLevel();
		setLevel(Level.OFF);
		try {
			if (randomFile != null) {
				randomFile.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		File file = new File(pattern);
		if (file.isFile()) {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
			String tag = sdf.format(new Date());
			String fileName = file.getName();
			int lastIndex = fileName.lastIndexOf(".");
			file.renameTo(new File(file.getParentFile().getPath(),
					lastIndex > 0 ? (fileName.substring(0, lastIndex) + "_"
							+ tag + fileName.substring(lastIndex)) : file
							.getName() + "_" + tag + ".log"));
		}
		try {
			open(file);
		} catch (IOException ix) {
			reportError(null, ix, ErrorManager.OPEN_FAILURE);

		}
		setLevel(oldLevel);
	}

	/**
	 * Format and publish a <tt>LogRecord</tt>.
	 * 
	 * @param record
	 *            description of the log event. A null record is silently
	 *            ignored and is not published
	 */
	public synchronized void publish(LogRecord record) {
		if (!isLoggable(record)) {
			return;
		}
		long length = 0;
		try {
			length = randomFile.length();
			randomFile.seek(length);// 确保每次都是在文件末尾添加数据
		} catch (IOException e) {
			e.printStackTrace();
		}
		String msg;
		try {
			msg = getFormatter().format(record);
		} catch (Exception ex) {
			reportError(null, ex, ErrorManager.FORMAT_FAILURE);
			return;
		}
		try {
			if (!doneHeader) {
				randomFile.write(getFormatter().getHead(this).getBytes());
				doneHeader = true;
			}
			randomFile.write(msg.getBytes());
		} catch (Exception ex) {
			reportError(null, ex, ErrorManager.WRITE_FAILURE);
		}
		if (limit > 0 && length + randomFile.getWriteSizeAndClear() >= limit) {
			rotate();
		}
	}

	@Override
	public boolean isLoggable(LogRecord record) {

		int levelValue = getLevel().intValue();
		if (record.getLevel().intValue() < levelValue
				|| levelValue == Level.OFF.intValue()) {
			return false;
		}
		Filter filter = getFilter();
		if (filter == null) {
			return true;
		}
		return filter.isLoggable(record);

	}

	/**
	 * Close all the files.
	 * 
	 * @exception SecurityException
	 *                if a security manager exists and if the caller does not
	 *                have <tt>LoggingPermission("control")</tt>.
	 */
	public synchronized void close() throws SecurityException {
		try {
			randomFile.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	class InnerRandomAccessFile extends RandomAccessFile {
		int writeSize;

		public InnerRandomAccessFile(File file, String mode)
				throws FileNotFoundException {
			super(file, mode);
		}

		@Override
		public void write(byte[] b) throws IOException {
			super.write(b);
			writeSize = b.length;
		}

		public int getWriteSizeAndClear() {
			try {
				return writeSize;
			} finally {
				writeSize = 0;
			}
		}
	}

	private static class InitializationErrorManager extends ErrorManager {
		Exception lastException;

		public void error(String msg, Exception ex, int code) {
			lastException = ex;
		}
	}

	String getStringProperty(String val, String defaultValue) {
		if (val == null) {
			return defaultValue;
		}
		return val.trim();
	}

	private int getIntProperty(String val, int defaultValue) {
		if (val == null) {
			return defaultValue;
		}
		try {
			return Integer.parseInt(val.trim());
		} catch (Exception ex) {
			return defaultValue;
		}
	}

	private boolean getBooleanProperty(String val, boolean defaultValue) {
		if (val == null) {
			return defaultValue;
		}
		val = val.toLowerCase();
		if (val.equals("true") || val.equals("1")) {
			return true;
		} else if (val.equals("false") || val.equals("0")) {
			return false;
		}
		return defaultValue;
	}

	private Level getLevelProperty(String val, Level defaultValue) {
		if (val == null) {
			return defaultValue;
		}
		try {
			return Level.parse(val.trim());
		} catch (Exception ex) {
			return defaultValue;
		}
	}

	private Filter getFilterProperty(String val, Filter defaultValue) {
		try {
			if (val != null) {
				Class<?> clz = ClassLoader.getSystemClassLoader()
						.loadClass(val);
				return (Filter) clz.newInstance();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return defaultValue;
	}

	private Formatter getFormatterProperty(String val, Formatter defaultValue) {
		try {
			if (val != null) {
				Class<?> clz = ClassLoader.getSystemClassLoader()
						.loadClass(val);
				return (Formatter) clz.newInstance();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return defaultValue;
	}

}
